class Rudoku::DslProxy
  def self.declarator(*methods)
    methods.each do |method|
      define_method(method) do |arg|
        instance_variable_set("@#{method}", arg)
      end
    end
  end
  
  def self.expose(inst_var, methods)
    methods.each do |method|
      define_method(method) do |*args|
        instance_variable_get("@#{inst_var}").send(method, *args)
      end
    end
  end
  
  def run(&blk)
    self.instance_eval(&blk)
    self
  end
end


class Rudoku::RuleDslProxy < Rudoku::DslProxy
  # extraction  
  def rule
    Rule.new(:id => @id, :name => @name, :desc => @desc, :reason => @reason, &@logic) # constructs a new rule object from the collected data
  end
  
  # exposure
  declarator :id, :name, :desc, :reason
  
  def logic(&blk)
    @logic = blk
  end
end


class Rudoku::RuleLogicDslProxy < Rudoku::DslProxy
  def initialize(rule, sudoku)
    @rule, @sudoku = rule, sudoku
    @eliminations = []
  end
  
  def run
    super(&@rule.logic)
  end
  
  # extraction
  attr_reader :eliminations
  
  # exposure
  def eliminate(fields, candidates, params = nil)
    fields = [fields] unless fields.kind_of? Array
    candidates = [candidates] unless candidates.kind_of? Array
    
    fields.each do |field|
      candidates.each do |candidate|
        @eliminations << Rudoku::Elimination.new(@rule, field, candidate, params) if field.possibilities.include?(candidate)
      end
    end
  end
  
  def set(fields, candidates, params = nil)
    candidates = [candidates] unless candidates.kind_of? Array
    eliminate(fields, [*1..9] - candidates, params)
  end
  
  def abort
    throw :abort
  end
  
  # also, expose all the relevant methods of the Sudoku class (field accessors and others)
  expose :sudoku, [:houses, :rows, :columns, :lines, :squares, :fields, :chutes, :towers, :floors,
                   :solved_fields, :unsolved_fields, :containing_square_pos, :containing_square]
end


class Rudoku::FieldSelectorDslProxy < Rudoku::DslProxy
  # extraction
  def field_selector
    FieldSelector.new(:id => @id, :name => @name, :desc => @desc, :reason => @reason, :fallback => @fallback, &@logic)
  end
  
  # exposure
  declarator :id, :name, :desc, :reason
  
  def fallback(fb)
    @fallback = (fb.kind_of? Symbol) ? FieldSelector[fb] : fb
  end
  
  def logic(&blk)
    @logic = blk
  end
end

class Rudoku::FieldSelectorLogicDslProxy < Rudoku::DslProxy
  def initialize(selector, sudoku)
    @selector, @sudoku = selector, sudoku
  end
  
  def run
    super(&@selector.logic)
  end
  
  # extraction
  attr_reader :field_selection
   
  # exposure
  def select(field, params = nil)
    @field_selection = Rudoku::FieldSelection.new(@selector, field, params)
  end
  
  # also, expose all the relevant methods of the Sudoku class (field accessors and others)
  expose :sudoku, [:houses, :rows, :columns, :lines, :squares, :fields, :chutes, :towers, :floors,
                   :solved_fields, :unsolved_fields, :containing_square_pos, :containing_square]
end

class Rudoku::CandidateSelectorDslProxy < Rudoku::DslProxy
  # extraction
  def candidate_selector
    CandidateSelector.new(:id => @id, :name => @name, :desc => @desc, :reason => @reason, :fallback => @fallback, &@logic)
  end
  
  # exposure
  declarator :id, :name, :desc, :reason
  
  def fallback(fb)
    @fallback = (fb.kind_of? Symbol) ? CandidateSelector[fb] : fb
  end
  
  def logic(&blk)
    @logic = blk
  end
end

class Rudoku::CandidateSelectorLogicDslProxy < Rudoku::DslProxy
  def initialize(selector, field)
    @selector, @field = selector, field
  end
  
  def run
    super(&@selector.logic)
  end
  
  # extraction
  attr_reader :candidate_selection
  
  # exposure
  def select(candidate, params = nil)
    @candidate_selection = Rudoku::CandidateSelection.new(@selector, @field, candidate, params)
  end
  
  expose :field, [:possibilities, :x, :y, :sudoku, :position, :containing_square_pos, :containing_square,
                  :containing_column, :containing_row, :containing_houses, :seen_fields, :seen_fields_prioritized, :sees?] # TODO: invullen
end


def rule(&blk)
  Rudoku::RuleDslProxy.new.run(&blk).rule
end

def field_selector(&blk)
  Rudoku::FieldSelectorDslProxy.new.run(&blk).field_selector
end

def candidate_selector(&blk)
  Rudoku::CandidateSelectorDslProxy.new.run(&blk).candidate_selector
end
